<?php
/***
* @version $Id: class_template.php 336 2007-01-23 08:12:56Z flexiondotorg $
* @copyright (c) 2006 - 2007 Flexion.Org
*            (c) 2004 - 2006 CyberAlien (http://www.phpbbstyles.com)
*            (c) 2004 - 2005 Project Minerva
*            (c) 2003 - 2004 Xore
*            (c) 2001 - 2006 phpBB Group
* @license   http://opensource.org/licenses/gpl-license.php GNU Public License
*
* Template class originally written By Nathan Codding of the phpBB group.
* The interface was originally inspired by PHPLib templates, and the template
* file formats are quite similar.
*
* IF, ELSEIF, ENDIF tags are backported from phpBB 2.2
*
***/

if ( !defined('IN_R3BORN') )
{
	die('Hacking attempt');
}

// templates directory
define('TPL_START', 'templates/');
define('TPL_ANY', '/templates/');

// internal definitions. do not edit.
define('TPL_TAG_NONE', 0);
define('TPL_TAG_PHP', 1);
define('TPL_TAG_BEGIN', 2);
define('TPL_TAG_END', 3);
define('TPL_TAG_INCLUDE', 4);
define('TPL_TAG_IF', 5);
define('TPL_TAG_ELSE', 6);
define('TPL_TAG_ELSEIF', 7);
define('TPL_TAG_ENDIF', 8);
define('TPL_TAG_DEFINE', 9);
define('TPL_TAG_UNDEFINE', 10);
define('TPL_TAG_BEGINELSE', 11);

class Template {
	var $classname = 'Template';

	// variable that holds all the data we'll be substituting into
	// the compiled templates.
	// ...
	// This will end up being a multi-dimensional array like this:
	// $this->_tpldata[block.][iteration#][child.][iteration#][child2.][iteration#][variablename] == value
	// if it's a root-level variable, it'll be like this:
	// $this->vars[varname] == value  or  $this->_tpldata['.'][0][varname] == value
	// array "vars" is added for easier access to data

//	var $_tpldata = array();
	var $_tpldata = array('.' => array(0 => array()));
	var $vars;

	// Hash of filenames for each template handle.
	var $files = array();

	// Root template directory and old root template directory (used when switching in and out of module templates for blocks and hooks)
	var $root = '';
	var $old_root = '';

	// Current template
	var $tpl = '';

	// Current template name
	var $tplname = '';

	// Template root directory (generated by set_rootdir)
	var $tpldir = '';
	var $tpldir_len = 0;

	// Default template directory.
	// If a .tpl for current style isn't found then the .tpl of the same name will be used from the style set in $tpldef.
	var $tpldef = 'subAndreas';

	// counter for include
	var $include_count = 0;

	// Cache directory
	var $cache_dir = '';

	// Cache filename prefix.
	var $cache_prefix = '';

	// Template cache directory
	var $cache_root = 'cache/';

	// Cache settings
	// Counter for how many times we hit a cached template
	var $cache_hits = 0;

	// Counter for how many times we had to cache a template
	var $cache_misses = 0;

	// this will hash handle names to the compiled code for that handle.
	var $compiled_code = array();

	// This will hold the uncompiled code for that handle.
	var $uncompiled_code = array();

	/**
	 * Constructor. Simply sets the root dir.
	 *
	 */
	function Template($root = '.')
	{
		global $config, $phpEx;

		// setting pointer "vars"
		$this->vars = &$this->_tpldata['.'][0];

		$this->set_rootdir($root);

		// adding current template
		$this->tpl = $this->root . '/';
		if(substr($this->tpl, 0, 2) === './')
		{
			$this->tpl = substr($this->tpl, 2, strlen($this->tpl));
		}

		// file extension with session ID (eg: "php?sid=123&" or "php?")
		// can be used to make custom URLs without modifting R3-born
		// contains "&" or "?" at the end so you can easily append paramenters
		$php = append_sid($phpEx);
		if(strpos($php, '?'))
		{
			$php .= '&';
		}
		else
		{
			$php .= '?';
		}
		$this->vars['PHP'] = $php;
		$this->vars['LANG'] = $config['default_lang'];
		$this->vars['TEMPLATE'] = $this->tpl;
		$this->vars['TEMPLATE_NAME'] = $this->tplname;
	}

	/**
	 * Destroys this template object. Should be called when you're done with it, in order
	 * to clear out the template data so you can load/parse a new template set.
	 */
	function destroy()
	{
		//$this->_tpldata = array();
		$this->_tpldata = array('.' => array(0 => array()));
		$this->vars = &$this->_tpldata['.'][0];
	}

	/**
	 * Sets the root directory for this Template object.
	 */
	function set_rootdir($dir)
	{
		global $root_path;

		if (!@is_dir($dir))
		{
			return false;
		}

		$dir = str_replace('\\', '/', $dir);

		$this->cache_prefix = '';

		$this->tpldir = $root_path . 'templates/';
		$this->tpldir_len = strlen($this->tpldir);

		$this->root = $dir;
		$this->tplname = $this->template_name($dir);

		$this->cache_dir = $root_path . $this->cache_root . substr($dir, strrpos($dir, '/')) . '/';

		if(!@is_dir($this->cache_dir))
		{
			@umask(0);
			@mkdir($this->cache_dir, 0777);
		}

		return true;
	}

	/**
	 * Sets the modules root directory for this Template object.
	 */
	function set_modulerootdir($module_name)
	{
		global $root_path;

		$dir = $root_path . 'modules/' . $module_name . '/templates/' . $this->tplname;

		if (!@is_dir($dir))
		{
			return false;
		}

		$this->old_root = $this->root;

		$dir = str_replace('\\', '/', $dir);

		$this->cache_prefix = $module_name . '_';

		$this->tpldir = $root_path . 'modules/' . $module_name . '/templates/';
		$this->tpldir_len = strlen($this->tpldir);

		$this->root = $dir;
		$this->tplname = $this->template_name($dir);

		return true;
	}

	/**
	 * Extracts template name from path
	 */
	function template_name($dir)
	{
		$tpl = TPL_ANY; // can start at any position
		$tpl_null = TPL_START; // can start only at zero position
		// searching for 'templates/' and removing everything before it
		$pos = strpos($dir, $tpl);
		if($pos === false)
		{
			if(substr($dir, 0, strlen($tpl_null)) !== $tpl_null)
			{
				return '';
			}
			$str = substr($dir, strlen($tpl_null), strlen($dir));
		}
		else
		{
			$str = substr($dir, $pos + strlen($tpl), strlen($dir));
		}
		// searching for one more 'templates/'
		// that can happen if full path is like /home/some_dude/templates/phpbb/templates/subAndreas/
		$dir = $this->template_name($str);
		if(!$dir)
		{
			$dir = $str;
		}
		if(strpos($str, $tpl) !== false)
		{
			$dir = $this->template_name($str);
		}
		// check for another subdirectory
		$pos = strpos($dir, '/');
		if($pos)
		{
			$dir = substr($dir, 0, $pos);
		}
		return $dir;
	}

	/**
	 * Generates a full path+filename for the given filename, which can either
	 * be an absolute name, or a name relative to the rootdir for this Template
	 * object.
	 */
	function make_filename($filename)
	{
		global $root_path, $mvModuleTemplates, $mvModule_root_path, $mvModuleName;

		$filename = str_replace('\\', '/', $filename);

		// Check if it's an absolute or relative path.
		if ((substr($filename, 0, 1) !== '/') && (substr($filename, 1, 1) !== ':'))
		{
			if (in_array($filename, $mvModuleTemplates))
			{
				$this->tpldir = $mvModule_root_path . 'templates/';
				$this->cache_prefix = $mvModuleName . '_';

			    if (defined('IN_ADMIN'))
    			{
    				$filename = $mvModule_root_path . 'templates/admin/' . $filename;
    			}
    			else
    			{
					$filename = $mvModule_root_path . 'templates/' . $this->tplname . '/' . $filename;
				}
			}
			else
			{
				$this->tpldir = $root_path . 'templates/';
				$filename = $this->root . '/' . $filename;
			}
		}

		return $filename;
	}

	/**
	 * Sets the template filenames for handles. $filename_array
	 * should be a hash of handle => filename pairs.
	 */
	function set_filenames($filename_array)
	{
		if (!is_array($filename_array))
		{
			return false;
		}

		foreach($filename_array as $handle => $filename)
		{
			$this->set_filename($handle, $filename);
		}

		return true;
	}

	function set_filename($handle, $filename, $tpl_include = false, $quiet = false)
	{
		global $config;
		global $mvModuleTemplates, $mvModule_root_path, $mvModuleName;

		$this->filename[$handle] = $filename;
		$this->files[$handle] = $this->make_filename($filename);

		// checking if we have valid filename
		if(!$this->files[$handle])
		{
			if($tpl_include || $quiet)
			{
				return false;
			}
			else
			{
				die('Template->make_filename(): Error - invalid template ' . $filename);
			}
		}

		// checking if tpl and/or php file exists
		if(!@file_exists($this->files[$handle]))
		{
			// trying to load alternative filename (usually subAndreas)
			if(!empty($this->tpldef) && !empty($this->tplname) && ($this->tpldef !== $this->tplname))
			{
				$this->files[$handle] = '';
				// save old configuration
				$root = $this->root;
				$tplname = $this->tplname;

				// set temporary configuration
				$this->root = $this->tpldir . $this->tpldef;

				$this->tplname = $this->tpldef;

				// recursively run set_filename
				$res = $this->set_filename($handle, $filename, $tpl_include, $quiet);

				// restore old configuration
				$this->root = $root;
				$this->tplname = $tplname;
				return $res;
			}

			if($quiet)
			{
				return false;
			}

			if($tpl_include)
			{
				die('Template->make_filename(): Error - included template file not found: ' . $filename);
			}
			else
			{
				die('Template->make_filename(): Error - template file not found: ' . $filename);
			}
		}

		return true;
	}

	function write_cache($cache_file, $handle)
	{
		if ($fp = @fopen($cache_file, 'w'))
		{
			@fwrite ($fp, '<?php' . "\n" . $this->compiled_code[$handle] . "\n?" . '>');
			@fclose($fp);
			@touch($cache_file, @filemtime($this->files[$handle]));
			@chmod($cache_file, 0777);
			return true;
		}
		else
		{
			return false;
		}
	}

	/**
	 * Load the file for the handle, compile the file,
	 * and run the compiled code. This will print out
	 * the results of executing the template.
	 */
	function pparse($handle)
	{
		global $phpEx;

		$cache_file = $this->cache_dir . $this->cache_prefix . $this->filename[$handle] . '.' . $phpEx;

		// Has the .tpl been modified since we cached it?
		// If not, include the cached file. If the .tpl is newer then re-evaulte and cache it.
		if(@filemtime($cache_file) == @filemtime($this->files[$handle]))
		{
			$this->cache_hits++;

			$_str = '';
			@include($cache_file);

			if ($_str != '')
			{
				echo $_str;
			}
		}
		else
		{
			if (!$this->loadfile($handle))
			{
				die('Template->pparse(): Couldn\'t load template file for handle' . $handle);
			}

			$this->cache_misses++;

			if (empty($this->compiled_code[$handle]))
			{
				// Actually compile the code now.
				$this->compiled_code[$handle] = $this->compile($this->uncompiled_code[$handle]);
			}

			$this->write_cache($cache_file, $handle);

			eval($this->compiled_code[$handle]);
		}

		return true;

	}

	/**
	* Assign a single variable to a single key
	* @access public
	*
	 * Root-level variable assignment. Adds to current assignments, overriding
	 * any existing variable assignment with the same name.
	 */
	function assign_var($varname, $varval)
	{
		$this->vars[$varname] = $varval;

		return true;
	}

	/**
	* Assign key variable pairs from an array
	* @access public
	*
	 * Root-level variable assignment. Adds to current assignments, overriding
	 * any existing variable assignment with the same name.
	 */
	function assign_vars($vararray)
	{
		if (!is_array($vararray))
		{
			return false;
		}

		foreach($vararray as $key => $val)
		{
			$this->vars[$key] = $val;
		}

		return true;
	}

	/**
	 * Inserts the uncompiled code for $handle as the
	 * value of $varname in the root-level. This can be used
	 * to effectively include a template in the middle of another
	 * template.
	 * Note that all desired assignments to the variables in $handle should be done
	 * BEFORE calling this function.
	 *
	 * @access public
	 */
	function assign_var_from_handle($varname, $handle)
	{
		ob_start();
		$res = $this->pparse($handle);
		$this->vars[$varname] = ob_get_contents();
		ob_end_clean();

		return $res;
	}

	/**
	* Assign key variable pairs from an array to a specified block
	* @access public
	*
	 * Block-level variable assignment. Adds a new block iteration with the given
	 * variable assignments. Note that this should only be called once per block
	 * iteration.
	 */
	function assign_block_vars($blockname, $vararray)
	{
		if (strstr($blockname, '.'))
		{
			// Nested block.
			$blocks = explode('.', $blockname);
			$blockcount = sizeof($blocks) - 1;

			$str = &$this->_tpldata;

			for ($i = 0; $i < $blockcount; $i++)
        	{
            	$str = &$str[ $blocks[$i].'.' ];
		        $str = &$str[ sizeof($str)-1 ];
			}

			$s_row_count = isset($str[$blocks[$blockcount.'.']]) ? sizeof($str[$blocks[$blockcount.'.']]) : 0;
			$vararray['S_ROW_COUNT'] = $s_row_count;
			$vararray['S_LAST_ROW'] = true;

			if (!$s_row_count)
			{
				// Assign S_FIRST_ROW
				$vararray['S_FIRST_ROW'] = true;
			}
			//if ($s_row_count > 0)
			else
			{
				// We always assign S_LAST_ROW and remove the entry before
				unset($str[$blocks[$blockcount.'.']][($s_row_count - 1)]['S_LAST_ROW']);
			}

        	// Now we add the block that we're actually assigning to.
	        // We're adding a new iteration to this block with the given
	        // variable assignments.
        	$str[ $blocks[$blockcount].'.' ][] = $vararray;
		}
		else
		{
			// Top-level block.
			$s_row_count = (isset($this->_tpldata[$blockname.'.'])) ? sizeof($this->_tpldata[$blockname.'.']) : 0;
			$vararray['S_ROW_COUNT'] = $s_row_count;
			$vararray['S_LAST_ROW'] = true;

			if (!$s_row_count)
			{
				// Assign S_FIRST_ROW
				$vararray['S_FIRST_ROW'] = true;
			}
			//if ($s_row_count > 0)
			else
			{
				// We always assign S_LAST_ROW and remove the entry before
				unset($this->_tpldata[$blockname.'.'][($s_row_count - 1)]['S_LAST_ROW']);
			}

			// Add a new iteration to this block with the variable assignments
			// we were given.
			$this->_tpldata[$blockname.'.'][] = $vararray;
		}

		return true;
	}

	/**
	 * Block-level variable re-assignment. Prevents new block iteration with the given
	 * variable assignments. Note that once you've iterated to a new block via assign_block_vars,
	 * you won't be able to come back to an old block.
	 */
	function reassign_block_vars($blockname, $vararray)
	{
		if (strstr($blockname, '.'))
		{
			// Nested block.
			$blocks = explode('.', $blockname);
			$blockcount = sizeof($blocks) - 1;

			$str = &$this->_tpldata;

			for($i = 0; $i < $blockcount; $i++)
			{
				$str = &$str[$blocks[$i].'.'];
				$str = &$str[sizeof($str)-1];
			}

			// Now we add the block that we're actually assigning to.
			// We're adding a new iteration to this block with the given
			//   variable assignments.
			$str = &$str[$blocks[$blockcount].'.'];

			$count = sizeof($str) - 1;
			if($count >= 0)
			{
				// adding only if there is at least one item
				$str[$count] = array_merge($str[$count], $vararray);

				$s_row_count = isset($str[$blocks[$blockcount.'.']]) ? sizeof($str[$blocks[$blockcount.'.']]) : 0;
				$vararray['S_ROW_COUNT'] = $s_row_count;
				$vararray['S_LAST_ROW'] = true;

				if (!$s_row_count)
				{
					// Assign S_FIRST_ROW
					$vararray['S_FIRST_ROW'] = true;
				}
				//if ($s_row_count > 0)
				else
				{
					// We always assign S_LAST_ROW and remove the entry before
					unset($str[$blocks[$blockcount.'.']][($s_row_count - 1)]['S_LAST_ROW']);
				}
			}
		}
		else
		{
			// Top-level block.
			// Add a new iteration to this block with the variable assignments
			// we were given.
			$str = &$this->_tpldata[$blockname.'.'];

			$count = sizeof($str) - 1;
			if($count >= 0)
			{
				// adding only if there is at least one item
				$str[$count] = array_merge($str[$count], $vararray);

				$s_row_count = (isset($this->_tpldata[$blockname.'.'])) ? sizeof($this->_tpldata[$blockname.'.']) : 0;
				$vararray['S_ROW_COUNT'] = $s_row_count;
				$vararray['S_LAST_ROW'] = true;

				// Assign S_FIRST_ROW
				if (!$s_row_count)
				{
					$vararray['S_FIRST_ROW'] = true;
				}
				//if ($s_row_count > 0)
				else
				{
					// We always assign S_LAST_ROW and remove the entry before
					unset($this->_tpldata[$blockname.'.'][($s_row_count - 1)]['S_LAST_ROW']);
				}
			}
		}
		return true;
	}

	/**
	 * Inserts the uncompiled code for $handle as the
	 * value of $varname in the block-level. This can be used
	 * to effectively include a template in the middle of another
	 * template.
	 * Note that all desired assignments to the variables in $handle should be done
	 * BEFORE calling this function.
	 *
	* @access public
	 */
	function assign_block_var_from_handle($varname, $handle)
	{
		$this->assign_var_from_handle('_tmp', $handle);

		$lastposition = strrpos($varname,'.');
		$blockname = substr($varname,0,$lastposition);
		$varname = substr($varname,$lastposition+1);

		// assign the value of the generated variable to the given varname.
		$this->reassign_block_vars($blockname, array($varname => $this->vars['_tmp']));
		return true;
	}

	/**
	* Reset/empty complete block
	* @access public
	*
	 * Block-level variable clearing. Removes a block of data so it can be re-written
	 * fresh (for iterative file handled arrays, when different data is needed)
	 */
	function clear_block_var($blockname)
	{
		if (strstr($blockname, '.'))
		{
			// Nested block.
			$blocks = explode('.', $blockname);
			$blockcount = sizeof($blocks) - 1;

			$str = &$this->_tpldata;
	        for ($i = 0; $i < $blockcount; $i++)
        	{
	            $str = &$str[ $blocks[$i].'.' ];
	        	$str = &$str[ sizeof($str)-1 ];
        	}

         	//$str[ $blocks[$blockcount].'.' ][] = array();
			unset($str[$blocks[$blockcount].'.']);
		}
		else
		{
			// Top-level block.
			//$this->_tpldata[$blockname . '.'] = array();
			unset($this->_tpldata[$blockname.'.']);
		}

		return true;
	}

	/**
	 * Checks for empty variable and shows language variable if possible.
	 */
	function lang($var)
	{
		global $lang;

		if(substr($var, 0, 2) === 'L_')
		{
			// Check for L_ vars
			$var = substr($var, 2);
			// check variable as it is
			if(isset($lang[$var]))
			{
				return $lang[$var];
			}
			// check variable in lower case
			if(isset($lang[strtolower($var)]))
			{
				return $lang[strtolower($var)];
			}
			// check variable with first letter in upper case
			$str = ucfirst(strtolower($var));
			if(isset($lang[$str]))
			{
				return $lang[$str];
			}
			return ''; //str_replace('_', ' ', $var);
		}
		else if(substr($var, 0, 3) === 'LA_')
		{
			// Check for LA_ vars, these are slashed for use in Javascript.
			$var = substr($var, 3);
			// check variable as it is
			if(isset($lang[$var]))
			{
				return addslashes($lang[$var]);
			}
			// check variable in lower case
			if(isset($lang[strtolower($var)]))
			{
				return addslashes($lang[strtolower($var)]);
			}
			// check variable with first letter in upper case
			$str = ucfirst(strtolower($var));
			if(isset($lang[$str]))
			{
				return addslashes($lang[$str]);
			}
			return ''; //str_replace('_', ' ', $var);
		}
		return '';
	}

	/**
	 * If not already done, load the file for the given handle and populate
	 * the uncompiled_code[] hash with its code. Do not compile.
	 */
	function loadfile($handle)
	{
		// If the file for this handle is already loaded and compiled, do nothing.
		if (!empty($this->uncompiled_code[$handle]))
		{
			return true;
		}

		// If we don't have a file assigned to this handle, die.
		if (!isset($this->files[$handle]))
		{
			die('Template->loadfile(): No file specified for handle ' . $handle);
		}

		$filename = $this->files[$handle];

		$str = implode('', @file($filename));
		if (empty($str))
		{
			die('Template->loadfile(): File $filename for handle ' . $handle . ' is empty');
		}

		$this->uncompiled_code[$handle] = $str;

		return true;
	}

	/**
	 * Compiles the given string of code, and returns
	 * the result in a string.
	 * If "do_not_echo" is true, the returned code will not be directly
	 * executable, but can be used as part of a variable assignment
	 * for use in assign_code_from_handle().
	 */
	function compile($code, $do_not_echo = false, $retvar = '')
	{
		$code = ' ?'.'>' . $this->compile_code('', $code, true) . '<'."?php \n";
		if($do_not_echo)
		{
			$code = "ob_start();\n". $code. "\n\${$retvar} = ob_get_contents();\nob_end_clean();\n";
		}
		return $code;
	}


	/**
	 * Generates a reference to the given variable inside the given (possibly nested)
	 * block namespace. This is a string of the form:
	 * ' . $this->_tpldata['parent'][$_parent_i]['$child1'][$_child1_i]['$child2'][$_child2_i]...['varname'] . '
	 * It's ready to be inserted into an "echo" line in one of the templates.
	 * NOTE: expects a trailing "." on the namespace.
	 */
	function generate_block_varref($namespace, $varname, $use_isset = true)
	{
		// Strip the trailing period.
		$namespace = substr($namespace, 0, strlen($namespace) - 1);

		// Get a reference to the data block for this namespace.
		$varref = $this->generate_block_data_ref($namespace, true);
		// Prepend the necessary code to stick this in an echo line.

		// Append the variable reference.
		$varref .= '[\'' . $varname . '\']';

		if($use_isset)
		{
			$varref = '<'.'?php echo isset(' . $varref . ') ? ' . $varref . ' : \'\'; ?'.'>';
		}
		else
		{
			$varref = '<'.'?php echo ' . $varref . '; ?'.'>';
		}

		return $varref;

	}


	/**
	 * Generates a reference to the array of data values for the given
	 * (possibly nested) block namespace. This is a string of the form:
	 * $this->_tpldata['parent'][$_parent_i]['$child1'][$_child1_i]['$child2'][$_child2_i]...['$childN']
	 *
	 * If $include_last_iterator is true, then [$_childN_i] will be appended to the form shown above.
	 * NOTE: does not expect a trailing "." on the blockname.
	 */
	function generate_block_data_ref($blockname, $include_last_iterator, $defop = false)
	{
		// Get an array of the blocks involved.
		$blocks = explode('.', $blockname);
		$blockcount = sizeof($blocks) - 1;
		if($defop)
		{
			$varref = '$this->_tpldata[\'DEFINE\']';
			// Build up the string with everything but the last child.
			for ($i = 0; $i < $blockcount; $i++)
			{
				$varref .= "['" . $blocks[$i] . ".'][\$" . $blocks[$i] . '_i]';
			}
			// Add the block reference for the last child.
			$varref .= "['" . $blocks[$blockcount] . ".']";
			// Add the iterator for the last child if requried.
			if ($include_last_iterator)
			{
				$varref .= '[$' . $blocks[$blockcount] . '_i]';
			}
			return $varref;
		}
		if($include_last_iterator)
		{
			return '$'. $blocks[$blockcount]. '_item';
		}
		else
		{
			return '$'. $blocks[$blockcount-1]. '_item[\''. $blocks[$blockcount]. '.\']';
		}
	}

	function compile_code($filename, $code, $use_isset = false)
	{
		//	$filename - file to load code from. used if $code is empty
		//	$code - tpl code
		//	$use_isset - if false then compiled code looks more beautiful and easier
		//      to understand and it adds error_reporting() to supress php warnings.
		//      if true then isset() is used to check variables instead of supressing
		//	    php warnings. note: for extreme styles mod 2.x it works only for
		//		block variables and for usual variables its always true.

		// load code from file
		if(!$code && !empty($filename))
		{
			$code = @implode('', @file($filename));
		}

		// Replace phpBB 2.2 <!-- (END)PHP --> tags
		$search = array('<!-- PHP -->', '<!-- ENDPHP -->');
		$replace = array('<'.'?php ', ' ?'.'>');
		$code = str_replace($search, $replace, $code);

		// Break it up into lines and put " -->" back.
		$code_lines = explode(' -->', $code);
		$count = count($code_lines);
		for ($i = 0; $i < ($count - 1); $i++)
		{
			$code_lines[$i] .= ' -->';
		}

		$block_nesting_level = 0;
		$block_names = array();
		$block_names[0] = ".";
		$block_items = array();
		$count_if = 0;

		// prepare array for compiled code
		$compiled = array();
		//$count_bugs = count($this->bugs);

		// array of switches
		$sw = array();

		// replace all short php tags
		$new_code = array();
		$line_count = count($code_lines);
		for($i=0; $i<$line_count; $i++)
		{
			$line = $code_lines[$i];
			$pos = strpos($line, '<?');
			if($pos === false)
			{
				$new_code[] = $line;
				continue;
			}
			if(substr($line, $pos, 5) === '<?php')
			{
				// valid php tag. skip it
				$new_code[] = substr($line, 0, $pos + 5);
				$code_lines[$i] = substr($line, $pos + 5);
				$i --;
				continue;
			}
			// invalid php tag
			$new_code[] = substr($line, 0, $pos) . '<?php echo \'<?\'; ?>';
			$code_lines[$i] = substr($line, $pos + 2);
			$i --;
		}
		$code_lines = $new_code;

		// main loop
		$line_count = count($code_lines);
		for($i=0; $i<$line_count; $i++)
		{
			$line = $code_lines[$i];
			// reset keyword type
			$keyword_type = TPL_TAG_NONE;
			// check if we have valid keyword in current line
			$pos1 = strpos($line, '<!-- ');
			if($pos1 === false)
			{
				// no keywords in this line
				$compiled[] = $this->_compile_text($line, $use_isset);
				continue;
			}
			// find end of html comment
			$pos2 = strpos($line, ' -->', $pos1);
			if($pos2 !== false)
			{
				// find end of keyword in comment
				$pos3 = strpos($line, ' ', $pos1 + 5);
				if($pos3 !== false && $pos3 <= $pos2)
				{
					$keyword = substr($line, $pos1 + 5, $pos3 - $pos1 - 5);
					// check keyword against list of supported keywords. case-sensitive
					if($keyword === 'BEGIN')
					{
						$keyword_type = TPL_TAG_BEGIN;
					}
					elseif($keyword === 'END')
					{
						$keyword_type = TPL_TAG_END;
					}
					elseif($keyword === 'INCLUDE')
					{
						$keyword_type = TPL_TAG_INCLUDE;
					}
					elseif($keyword === 'IF')
					{
						$keyword_type = TPL_TAG_IF;
					}
					elseif($keyword === 'ELSE')
					{
						$keyword_type = TPL_TAG_ELSE;
					}
					elseif($keyword === 'ELSEIF')
					{
						$keyword_type = TPL_TAG_ELSEIF;
					}
					elseif($keyword === 'ENDIF')
					{
						$keyword_type = TPL_TAG_ENDIF;
					}
					elseif($keyword === 'DEFINE')
					{
						$keyword_type = TPL_TAG_DEFINE;
					}
					elseif($keyword === 'UNDEFINE')
					{
						$keyword_type = TPL_TAG_UNDEFINE;
					}
					elseif($keyword === 'BEGINELSE')
					{
						$keyword_type = TPL_TAG_BEGINELSE;
					}
				}
			}
			if(!$keyword_type)
			{
				// not valid keyword. process the rest of line
				$compiled[] = $this->_compile_text(substr($line, 0, $pos1 + 4), $use_isset);
				$code_lines[$i] = substr($line, $pos1 + 4);
				$i --;
				continue;
			}
			// remove code before keyword
			if($pos1 > 0)
			{
				$compiled[] = $this->_compile_text(substr($line, 0, $pos1), $use_isset);
			}
			// remove keyword
			$keyword_str = substr($line, $pos1, $pos2 - $pos1 + 4);
			$params_str = $pos2 == $pos3 ? '' : substr($line, $pos3 + 1, $pos2 - $pos3 - 1);
			$code_lines[$i] = substr($line, $pos2 + 4);
			$i--;
			// Check keywords

			/*
			* <!-- BEGIN -->
			*/
			if($keyword_type == TPL_TAG_BEGIN)
			{
				$params = explode(' ', $params_str);
				$num_params = count($params);
				// get variable name
				if($num_params == 1)
				{
					$var = $params[0];
				}
				elseif($num_params == 2)
				{
					if($params[0] === '')
					{
						$var = $params[1];
					}
					elseif($params[1] === '')
					{
						$var = $params[0];
					}
					else
					{
						// invalid tag
						$compiled[] = $keyword_str;
						continue;
					}
				}
				else
				{
					// invalid tag
					$compiled[] = $keyword_str;
					continue;
				}

				// adding code
				$block_nesting_level++;
				$block_names[$block_nesting_level] = $var;
				if(isset($block_items[$var]))
				{
					$block_items[$var] ++;
				}
				else
				{
					$block_items[$var] = 1;
				}
				if ($block_nesting_level < 2)
				{
					// Block is not nested.
					$line = '<'."?php\n\n";
					if($use_isset)
					{
						$line .= '$'. $var. '_count = ( isset($this->_tpldata[\''. $var. '.\']) ) ?  sizeof($this->_tpldata[\''. $var. '.\']) : 0;';
					}
					else
					{
						$line .= '$'. $var. '_count = sizeof($this->_tpldata[\''. $var. '.\']);';
					}
					$line .= "\n" . 'for ($'. $var. '_i = 0; $'. $var. '_i < $'. $var. '_count; $'. $var. '_i++)';
					$line .= "\n". '{'. "\n";
					$line .= ' $'. $var. '_item = &$this->_tpldata[\''. $var. '.\'][$'. $var. '_i];'."\n";
					//$line .= " \${$var}_item['S_ROW_COUNT'] = \${$var}_i;\n";
					//$line .= " \${$var}_item['S_NUM_ROWS'] = \${$var}_count;\n";
					$line .= "\n?".">";
				}
				else
				{
					// This block is nested.
					// Generate a namespace string for this block.
					$namespace = implode('.', $block_names);
					// strip leading period from root level..
					$namespace = substr($namespace, 2);
					// Get a reference to the data array for this block that depends on the
					// current indices of all parent blocks.
					$varref = $this->generate_block_data_ref($namespace, false);
					// Create the for loop code to iterate over this block.
					$line = '<'."?php\n\n";
					if($use_isset)
					{
						$line .= '$'. $var. '_count = ( isset('. $varref. ') ) ? sizeof('. $varref. ') : 0;';
					}
					else
					{
						$line .= '$'. $var. '_count = sizeof('. $varref. ');';
					}
					$line .= "\n". 'for ($'. $var. '_i = 0; $'. $var. '_i < $'. $var. '_count; $'. $var. '_i++)';
					$line .= "\n". '{'. "\n";
					$line .= ' $'. $var. '_item = &'. $varref. '[$'. $var. '_i];'."\n";
					//$line .= " \${$var}_item['S_ROW_COUNT'] = \${$var}_i;\n";
					//$line .= " \${$var}_item['S_NUM_ROWS'] = \${$var}_count;\n";
					$line .= "\n?".">";
				}
				$compiled[] = $line;
				continue;
			}
			/*
			* <!-- END -->
			*/
			if($keyword_type == TPL_TAG_END)
			{
				$params = explode(' ', $params_str);
				$num_params = count($params);
				if($num_params == 1)
				{
					$var = $params[0];
				}
				elseif($num_params == 2 && $params[0] === '')
				{
					$var = $params[1];
				}
				elseif($num_params == 2 && $params[1] === '')
				{
					$var = $params[0];
				}
				else
				{
					$compiled[] = $keyword_str;
					continue;
				}
				// We have the end of a block.
				$line = '<'."?php\n\n";
				$line .= '} // END ' . $var . "\n\n";
				$line .= 'if(isset($' . $var . '_item)) { unset($' . $var . '_item); } ';
				$line .= "\n\n?".">";
				if(isset($block_items[$var]))
				{
					$block_items[$var] --;
				}
				else
				{
					$block_items[$var] = -1;
				}
				unset($block_names[$block_nesting_level]);
				$block_nesting_level--;
				$compiled[] = $line;
				continue;
			}
			/*
			* <!-- BEGINELSE -->
			*/
			if($keyword_type == TPL_TAG_BEGINELSE)
			{
				if($block_nesting_level)
				{
					$var = $block_names[$block_nesting_level];
					$compiled[] = '<' . '?php } if(!$' . $var . '_count) { ?' . '>';
				}
				else
				{
					$compiled[] = $keyword_str;
					continue;
				}
			}
			/*
			* <!-- INCLUDE -->
			*/
			if($keyword_type == TPL_TAG_INCLUDE)
			{
				$params = explode(' ', $params_str);
				$num_params = count($params);
				if($num_params != 1)
				{
					$compiled[] = $keyword_str;
					continue;
				}
				$line = '<'.'?php ';
				$filehash = md5($params_str . $this->include_count . time());
				$line .= ' $this->set_filename(\'tpl_include_' . $filehash . '\', \'' . $params_str .'\', true); ';
				$line .= ' $this->pparse(\'tpl_include_' . $filehash . '\'); ';
				$line .= ' ?'.'>';
				$this->include_count ++;
				$compiled[] = $line;
				continue;
			}
			/*
			* <!-- IF -->
			*/
			if($keyword_type == TPL_TAG_IF || $keyword_type == TPL_TAG_ELSEIF)
			{
				if(!$count_if)
				{
					$keyword_type = TPL_TAG_IF;
				}
				$str = $this->compile_tag_if($params_str, $keyword_type == TPL_TAG_IF ? false : true);
				if($str)
				{
					$compiled[] = '<?php ' . $str . ' ?>';
					if($keyword_type == TPL_TAG_IF)
					{
						$count_if ++;
					}
				}
				else
				{
					$compiled[] = $keyword_str;
				}
				continue;
			}
			/*
			* <!-- ELSE -->
			*/
			if($keyword_type == TPL_TAG_ELSE && $count_if > 0)
			{
				$compiled[] = '<?php } else { ?>';
				continue;
			}
			/*
			* <!-- ENDIF -->
			*/
			if($keyword_type == TPL_TAG_ENDIF && $count_if > 0)
			{
				$compiled[] = '<?php } ?>';
				$count_if --;
				continue;
			}
			/*
			* <!-- DEFINE -->
			*/
			if($keyword_type == TPL_TAG_DEFINE)
			{
				$str = $this->compile_tag_define($params_str);
				if($str)
				{
					$compiled[] = '<?php ' . $str . ' ?>';
				}
				else
				{
					$compiled[] = $keyword_str;
				}
			}
			/*
			* <!-- UNDEFINE -->
			*/
			if($keyword_type == TPL_TAG_UNDEFINE)
			{
				$str = $this->compile_tag_undefine($params_str);
				if($str)
				{
					$compiled[] = '<?php ' . $str . ' ?>';
				}
				else
				{
					$compiled[] = $keyword_str;
				}
			}
		}

		// bring it back into a single string.
		$code_header = '';
		$code_footer = '';
		if(!$use_isset)
		{
			$code_header =	"<". "?php\n\$old_level = @error_reporting(E_ERROR | E_WARNING | E_PARSE); \n?".">";
			$code_footer = '<'."?php @error_reporting(\$old_level); ?".'>';
		}

		return $code_header . implode('', $compiled) . $code_footer;
	}

	/*
	* Compile code between tags
	*/
	function _compile_text($code, $use_isset)
	{
		if(strlen($code) < 3)
		{
			return $code;
		}
		// change template varrefs into PHP varrefs
		// This one will handle varrefs WITH namespaces
		$varrefs = array();
		preg_match_all('#\{(([a-z0-9\-_]+?\.)+?)([a-z0-9\-_]+?)\}#is', $code, $varrefs);
		$varcount = sizeof($varrefs[1]);
		$search = array();
		$replace = array();
		for ($i = 0; $i < $varcount; $i++)
		{
			$namespace = $varrefs[1][$i];
			$varname = $varrefs[3][$i];
			$new = $this->generate_block_varref($namespace, $varname, $use_isset);
			$search[] = $varrefs[0][$i];
			$replace[] = $new;
		}
		if(count($search) > 0)
		{
			$code = str_replace($search, $replace, $code);
		}
		// This will handle the remaining root-level varrefs
		$code = preg_replace('#\{([a-z0-9\-_]*?)\}#is', '<'.'?php echo isset($this->vars[\'\1\']) ? $this->vars[\'\1\'] : $this->lang(\'\1\'); ?'.'>', $code);
		$code = preg_replace('#\{\$([a-z0-9\-_]*?)\}#is', '<'.'?php echo isset($this->_tpldata[\'DEFINE\'][\'.\'][\'\\1\']) ? $this->_tpldata[\'DEFINE\'][\'.\'][\'\\1\'] : \'\'; ?'.'>', $code);
		return $code;
	}

	//
	// Compile IF tags - much of this is from Smarty with
	// some adaptions for our block level methods
	//
	function compile_tag_if($tag_args, $elseif)
	{
        /* Tokenize args for 'if' tag. */
        preg_match_all('/(?:
                         "[^"\\\\]*(?:\\\\.[^"\\\\]*)*"         |
                         \'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\'     |
                         [(),]                                  |
                         [^\s(),]+)/x', $tag_args, $match);

        $tokens = $match[0];
        $is_arg_stack = array();

        for ($i = 0; $i < count($tokens); $i++)
		{
			$token = &$tokens[$i];

			switch ($token)
			{
		                case '!':
		                case '%':
                		case '!==':
		                case '==':
                		case '===':
		                case '>':
                		case '<':
		                case '!=':
		                case '<>':
                		case '<<':
		                case '>>':
                		case '<=':
		                case '>=':
                		case '&&':
		                case '||':
				case '|':
				case '^':
				case '&':
				case '~':
				case ')':
				case ',':
				case '+':
				case '-':
				case '*':
				case '/':
				case '@':
					break;

				case 'eq':
					$token = '==';
					break;

				case 'ne':
				case 'neq':
					$token = '!=';
					break;

				case 'lt':
					$token = '<';
					break;

				case 'le':
				case 'lte':
					$token = '<=';
					break;

				case 'gt':
					$token = '>';
					break;

				case 'ge':
				case 'gte':
					$token = '>=';
					break;

				case 'and':
					$token = '&&';
					break;

				case 'or':
					$token = '||';
					break;

				case 'not':
					$token = '!';
					break;

				case 'mod':
					$token = '%';
					break;

				case '(':
					array_push($is_arg_stack, $i);
					break;

				case 'is':
					$is_arg_start = ($tokens[$i-1] == ')') ? array_pop($is_arg_stack) : $i-1;
					$is_arg	= implode('	', array_slice($tokens,	$is_arg_start, $i -	$is_arg_start));

					$new_tokens	= $this->_parse_is_expr($is_arg, array_slice($tokens, $i+1));

					array_splice($tokens, $is_arg_start, count($tokens), $new_tokens);

					$i = $is_arg_start;

				default:
					if (preg_match('#^(([a-z0-9\-_]+?\.)+?)?(\$)?([A-Z]+[A-Z0-9\-_]+)$#s', $token, $varrefs))
					{
						$token = (!empty($varrefs[1])) ? $this->generate_block_data_ref(substr($varrefs[1], 0, -1), true, $varrefs[3]) . '[\'' . $varrefs[4] . '\']' : (($varrefs[3]) ? '$this->_tpldata[\'DEFINE\'][\'.\'][\'' . $varrefs[4] . '\']' : '$this->vars[\'' . $varrefs[4] . '\']');
					}
					break;
            }
        }

		$code = (($elseif) ? '} elseif (' : 'if (') . (implode(' ', $tokens) . ') { ');

		return $code;
	}

	// This is from Smarty
	function _parse_is_expr($is_arg, $tokens)
	{
		$expr_end =	0;
		$negate_expr = false;

		if (($first_token = array_shift($tokens)) == 'not')
		{
			$negate_expr = true;
			$expr_type = array_shift($tokens);
		}
		else
		{
			$expr_type = $first_token;
		}

		switch ($expr_type)
		{
			case 'even':
				if (@$tokens[$expr_end] == 'by')
				{
					$expr_end++;
					$expr_arg =	$tokens[$expr_end++];
					$expr =	"!(($is_arg	/ $expr_arg) % $expr_arg)";
				}
				else
				{
					$expr =	"!($is_arg % 2)";
				}
				break;

			case 'odd':
				if (@$tokens[$expr_end] == 'by')
				{
					$expr_end++;
					$expr_arg =	$tokens[$expr_end++];
					$expr =	"(($is_arg / $expr_arg)	% $expr_arg)";
				}
				else
				{
					$expr =	"($is_arg %	2)";
				}
				break;

			case 'div':
				if (@$tokens[$expr_end] == 'by')
				{
					$expr_end++;
					$expr_arg =	$tokens[$expr_end++];
					$expr =	"!($is_arg % $expr_arg)";
				}
				break;

			default:
				break;
		}

		if ($negate_expr)
		{
			$expr =	"!($expr)";
		}

		array_splice($tokens, 0, $expr_end,	$expr);

		return $tokens;
	}


	function compile_tag_define($tag_args)
	{
		preg_match('#^(([a-z0-9\-_]+?\.)+?)?\$([A-Z][A-Z0-9_\-]*?) = (\'?)(.*?)(\'?)$#', $tag_args, $match);

		if (empty($match[3]) || empty($match[5]))
		{
			return '';
		}

		// Are we a string?
		if ($match[4] && $match[6])
		{
			$match[5] = "'" . addslashes(str_replace(array('\\\'', '\\\\'), array('\'', '\\'), $match[5])) . "'";
		}
		else
		{
			preg_match('#(true|false|\.)#i', $match[5], $type);

			switch (strtolower($type[1]))
			{
				case 'true':
				case 'false':
					$match[5] = strtoupper($match[5]);
					break;
				case '.';
					$match[5] = doubleval($match[5]);
					break;
				default:
					$match[5] = intval($match[5]);
					break;
			}
		}

		return (($match[1]) ? $this->generate_block_data_ref(substr($match[1], 0, -1), true, true) . '[\'' . $match[3] . '\']' : '$this->_tpldata[\'DEFINE\'][\'.\'][\'' . $match[3] . '\']') . ' = ' . $match[5] . ';';
	}

	function compile_tag_undefine($tag_args)
	{
		preg_match('#^(([a-z0-9\-_]+?\.)+?)?\$([A-Z][A-Z0-9_\-]*?)$#', $tag_args, $match);
		if (empty($match[3]))
		{
			return '';
		}
		return 'unset(' . (($match[1]) ? $this->generate_block_data_ref(substr($match[1], 0, -1), true, true) . '[\'' . $match[3] . '\']' : '$this->_tpldata[\'DEFINE\'][\'.\'][\'' . $match[3] . '\']') . ');';
	}

}

?>